{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "NvFMw17BoLbq"
   },
   "source": [
    "##### Copyright 2020 Google"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "cellView": "form",
    "id": "CvJ_QrYPoM8L"
   },
   "outputs": [],
   "source": [
    "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
    "# you may not use this file except in compliance with the License.\n",
    "# You may obtain a copy of the License at\n",
    "#\n",
    "# https://www.apache.org/licenses/LICENSE-2.0\n",
    "#\n",
    "# Unless required by applicable law or agreed to in writing, software\n",
    "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
    "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
    "# See the License for the specific language governing permissions and\n",
    "# limitations under the License."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "vcvcW1TMuqRP"
   },
   "source": [
    "# Quantum Chess REST API\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "OYeSZNZBoUK2"
   },
   "source": [
    "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
    "  <td>\n",
    "    <a target=\"_blank\" href=\"https://www.example.org/cirq/experiments/quantum_chess/quantum_chess_rest_api\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" />View on QuantumLib</a>\n",
    "  </td>\n",
    "  <td>\n",
    "    <a target=\"_blank\" href=\"https://colab.research.google.com/github/quantumlib/ReCirq/blob/master/docs/quantum_chess/quantum_chess_rest_api.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" />Run in Google Colab</a>\n",
    "  </td>\n",
    "  <td>\n",
    "    <a target=\"_blank\" href=\"https://github.com/quantumlib/ReCirq/blob/master/docs/quantum_chess/quantum_chess_rest_api.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" />View source on GitHub</a>\n",
    "  </td>\n",
    "  <td>\n",
    "    <a href=\"https://storage.googleapis.com/tensorflow_docs/ReCirq/docs/quantum_chess/quantum_chess_rest_api.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\" />Download notebook</a>\n",
    "  </td>\n",
    "</table>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "n5tEEhGGdTb0"
   },
   "source": [
    "[Quantum Chess](https://quantumchess.net/quantum-chess/) is a variant of chess that gives players access to extra moves which allow them to create superposition. All moves are applied to the game state via unitary evolution, allowing players to experience effects like superposition, entanglement, and interference. This project provides a limited implementation of the full Quantum Chess move set, executed on a set of qubits representing squares. The full Quantum Chess application requires an API for the chess UI to communicate with an external backend for move processing and calculation. In this notebook we will:\n",
    "* Set up an ascii board representation of a Quantum Chess game running on a Cirq simualtor.\n",
    "* Explore how to interact with the ascii board in interactive mode and by batching moves.\n",
    "* Implement the functionality of the Quantum Chess REST API\n",
    "* Start a simple server that serves REST endpoints, which could be used to hook up an instance of Quantum Chess to our Cirq implementation.\n",
    "\n",
    "For more information on how to implement Quantum Chess moves in Cirq, including qubit mapping and error correction, check out (this other notebook)\n",
    "\n",
    "First, install the Quantum Chess package from recirq."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "Im4myFbwd0E4"
   },
   "outputs": [],
   "source": [
    "!pip install -q git+https://github.com/quantumlib/ReCirq/"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "9emDSIkWLwAo"
   },
   "outputs": [],
   "source": [
    "import recirq\n",
    "import recirq.quantum_chess.ascii_board as ab\n",
    "\n",
    "b = ab.AsciiBoard()\n",
    "b.reset()\n",
    "print(b)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "8gN9Nbqg-Fhc"
   },
   "source": [
    "It is possible to play the game in interactive mode, by applying moves to the board. Split the knight on b1 to a3 and c3."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "6yqW_Y1z-THI"
   },
   "outputs": [],
   "source": [
    "from recirq.quantum_chess.move import Move\n",
    "from recirq.quantum_chess.enums import MoveType, MoveVariant\n",
    "\n",
    "m = Move(source='b1', target='a3', target2='c3', move_type=MoveType.SPLIT_JUMP, move_variant=MoveVariant.BASIC)\n",
    "b.reset()\n",
    "r = b.apply(m)\n",
    "print(b)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "FNFFv9-WA5De"
   },
   "source": [
    "In Quantum Chess, a move can be uniquely defined by up to 3 squares, a type, and a variant. \n",
    "\n",
    "Move types can take any value from the following set:\n",
    "```\n",
    "{ JUMP, SLIDE, SPLIT_JUMP, SPLIT_SLIDE, MERGE_JUMP, MERGE_SLIDE, PAWN_STEP, PAWN_TWO_STEP, PAWN_CAPTURE, PAWN_EP, KS_CASTLE, QS_CASTLE } \n",
    "```\n",
    "Jump type moves indicate there is no path to consider, like when knights move. Slide type moves must consider the squares along the sliding path. The Split versions are 3-qubit operations, that are designed to put a piece in superposition on two different targets. The Merge versions are just the inverse of a Split.\n",
    "\n",
    "Quantum Chess introduces the concept of a move variant. The variant of a move is determined by the state of the target square, or squares. Move variants can take any value from the following set:\n",
    "\n",
    "```{BASIC, CAPTURE, EXCLUDED}```\n",
    "\n",
    "A Basic variant is a move where the target square is unoccupied. A Capture variant is a move where the target square is occupied by a piece that can be captured by the piece being moved. An Excluded variant is a move where the target is occupied by a piece that cannot be captured. This can occur if a target is occupied by a same color piece in superposition. In both capture and excluded variants, a measurement will be performed. To learn more about move types, variants, and measurements, please see [this paper](https://arxiv.org/abs/1906.05836)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "Ha1eGYCtvtro"
   },
   "source": [
    "The ascii board is a convenience that can be used for testing the project imports.  The Quantum Chess REST API defines the interface, which used by the Quantum Chess Engine to assign an external resource to handle the quantum state of the game. This state encodes only the \"occupancy\" of each square on the board. Each square is mapped to a single qubit, where the state |1> corresponds to the square being occupied by a piece, and |0> is unoccupied. All piece type information, and rules checking, is handled classically within the Quantum Chess Engine. When implementing the API, we only neeed to use the CirqBoard. The following code shows how to initialize a CirqBoard with a single \"piece\" in square a1.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "y_NOwwjwfqsn"
   },
   "outputs": [],
   "source": [
    "from recirq.quantum_chess.quantum_board import CirqBoard\n",
    "from recirq.quantum_chess.bit_utils import ( bit_to_square, xy_to_bit )\n",
    "from recirq.quantum_chess.move import to_rank\n",
    "\n",
    "global_board = CirqBoard(1)\n",
    "\n",
    "def print_game(board):\n",
    "    board.print_debug_log()\n",
    "    print('\\n')\n",
    "    print(board)\n",
    "    print(\"\\n\\n\")\n",
    "\n",
    "probs = global_board.get_probability_distribution()\n",
    "\n",
    "print_game(global_board)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "s-B1gYs9lo0X"
   },
   "source": [
    "## The REST API\n",
    "\n",
    "The Quantum Chess REST API defines an interface for [REST](https://restfulapi.net/) endpoints that the Quantum Chess Engine can be directed to use when interacting with the quantum state of the game. The API declares an interface for three functions:\n",
    "* init\n",
    "* do_move\n",
    "* undo_move\n",
    "\n",
    "All three endpoints must return a json object with the following values:\n",
    "* probabilities: an array of 64 floating point numbers representing the probability of each square being occupied. Array indices are mapped to board squares starting from a1, and increasing along rows to h8.\n",
    "\n",
    "<center>\n",
    "<img src='./images/chess_board_indices.png' width=\"300\" >\n",
    "</center>\n",
    "\n",
    "* empty_bitboard: A bitboard with bits set to 1 for all squares known to be empty, i.e. 0% chance of being occupied.\n",
    "* full_bitboard: A bitboard with bits set to 1 for all squares known to be occupied, i.e. 100% chance of being occupied.\n",
    "\n",
    "A [bitboard](https://www.chessprogramming.org/Bitboards) is a 64-bit integer, where each bit corresponds to a square on the chess board. The bitboard is encoded in little endian form, with the least significant bit corresponding to a1, and increasing along rows up to h8 in the most significant bit.\n",
    "<center>\n",
    "<img src='./images/bitboard_order.png' width=\"700\" >\n",
    "</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "gYNJ6tMHnyl-"
   },
   "source": [
    "### Implement init\n",
    "\n",
    "The init function is used to initialize a quantum state to some classical starting position. It has the following code signature.\n",
    "```\n",
    "init(init_basis_state) : { probabilities, empty_bitboard, full_bitboard }\n",
    "```\n",
    "The single argument, init_basis_state, is a bitboard that represents the initial classical state of the board, i.e. which squares have a piece on them. The return value is a json object with three fields: probabilities, empty_bitboard, and full_bitboard.\n",
    "\n",
    "The following code defines an implementation of init that prints out the probability distribution of the initialized board, and returns the appropriate json."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "f182BGOkM-o1"
   },
   "outputs": [],
   "source": [
    "def init(board, init_basis_state):\n",
    "  board.with_state(init_basis_state)\n",
    "  probs = board.get_probability_distribution()\n",
    "  print_game(board)\n",
    "\n",
    "  return {\n",
    "      'probabilities' : probs,\n",
    "      'empty' : board.get_empty_squares_bitboard(),\n",
    "      'full' : board.get_full_squares_bitboard()\n",
    "  }\n",
    "\n",
    "r = init(global_board, 0xFFFF00000000FFFF)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "3K-eTI6HpElY"
   },
   "source": [
    "### Implement do_move\n",
    "\n",
    "The do_move function is used to apply a specific unitary to the qubits which correspond to the squares involved in the move. It has the following code signature\n",
    "```\n",
    "do_move( move ) : { probabilities, empty_bitboard, full_bitboard }\n",
    "```\n",
    " It takes a single argument, move, which is a json object with the following fields:\n",
    "* square1: integer index of the first square.\n",
    "* square2: integer index of the second square.\n",
    "* square3: integer index of the third square, only used for split and merge moves.\n",
    "* type: enumerated type of move with the following possible values\n",
    "```\n",
    "    NULL_TYPE = 0, UNSPECIFIED_STANDARD = 1, JUMP = 2, SLIDE = 3,\n",
    "    SPLIT_JUMP = 4, SPLIT_SLIDE = 5, MERGE_JUMP = 6, MERGE_SLIDE = 7,\n",
    "    PAWN_STEP = 8, PAWN_TWO_STEP = 9, PAWN_CAPTURE = 10, PAWN_EP = 11,\n",
    "    KS_CASTLE = 12, QS_CASTLE = 13 \n",
    "```\n",
    "* variant: enumerated variant of move with the following  possible values\n",
    "```\n",
    "    UNSPECIFIED = 0, BASIC = 1, EXCLUDED = 2, CAPTURE = 3\n",
    "```\n",
    "The return value is a json object with three fields: probabilities, empty_bitboard, and full_bitboard.\n",
    "\n",
    "The following code defines some helper functions to create Moves, which are used to apply specific unitaries to the qubits represented in the CirqBoard, and an implementation of do_move that will print the probability distribution after applying the move to the board."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "GcZBfAeWpQhJ"
   },
   "outputs": [],
   "source": [
    "from recirq.quantum_chess.move import Move\n",
    "from recirq.quantum_chess.enums import MoveType, MoveVariant\n",
    "\n",
    "# Helper function for creating a split move from json values\n",
    "def get_split_move(move_json):\n",
    "  return Move(\n",
    "    move_json['square1'],\n",
    "    move_json['square2'],\n",
    "    target2 = move_json['square3'],\n",
    "    move_type = MoveType(move_json['type']),\n",
    "    move_variant = MoveVariant(move_json['variant'])\n",
    "    )\n",
    "\n",
    "# Helper function for creating a merge move from json values\n",
    "def get_merge_move(move_json):\n",
    "  return Move(\n",
    "    move_json['square1'],\n",
    "    move_json['square3'],\n",
    "    source2 = move_json['square2'],\n",
    "    move_type = MoveType(move_json['type']),\n",
    "    move_variant = MoveVariant(move_json['variant'])\n",
    "    )\n",
    "\n",
    "# Helper function for creating a standard move from json values\n",
    "def get_standard_move(move_json):\n",
    "  return Move(\n",
    "    move_json['square1'],\n",
    "    move_json['square2'],\n",
    "    move_type = MoveType(move_json['type']),\n",
    "    move_variant = MoveVariant(move_json['variant'])\n",
    "    )\n",
    "    \n",
    "def do_move(board, move):\n",
    "  board.clear_debug_log()\n",
    "  r = board.do_move(move)\n",
    "  probs = board.get_probability_distribution()\n",
    "  print_game(board)\n",
    "\n",
    "  return {\n",
    "      'result' : r,\n",
    "      'probabilities' : probs,\n",
    "      'empty' : board.get_empty_squares_bitboard(),\n",
    "      'full' : board.get_full_squares_bitboard()\n",
    "  }\n",
    "\n",
    "move_json = {'square1' : 'b1', 'square2' : 'a3', 'square3' : 'c3', 'type' : MoveType.SPLIT_JUMP, 'variant': MoveVariant.BASIC}\n",
    "split_b1_a3_c3 = get_split_move(move_json)\n",
    "\n",
    "r = init(global_board, 0xFFFF00000000FFFF)\n",
    "r = do_move(global_board, split_b1_a3_c3)\n",
    "    \n",
    "        "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "I-qyQaOOqSDG"
   },
   "source": [
    "Notice, the circuit for the move is printed as well. This is made available in the board debug information. You can also see what happens when initializing the board using a noisy simulator with error mitigation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "Po_roPFkqfYj"
   },
   "outputs": [],
   "source": [
    "from recirq.quantum_chess.enums import ErrorMitigation\n",
    "from cirq import DensityMatrixSimulator, google\n",
    "from cirq.contrib.noise_models import DepolarizingNoiseModel\n",
    "\n",
    "NOISY_SAMPLER = DensityMatrixSimulator(noise= DepolarizingNoiseModel(\n",
    "    depol_prob=0.004))\n",
    "\n",
    "noisy_board = CirqBoard(0,\n",
    "                 sampler=NOISY_SAMPLER,\n",
    "                 device=google.Sycamore,\n",
    "                 error_mitigation= ErrorMitigation.Correct,\n",
    "                 noise_mitigation=0.05)\n",
    "\n",
    "r = init(noisy_board, 0xFFFF00000000FFFF)\n",
    "r = do_move(noisy_board, split_b1_a3_c3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "1DyVRbChsNZ5"
   },
   "source": [
    "You may notice that the circuit run discarded some of the returned samples due to error mitigation and post selection."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "DxaNPSsZvmrE"
   },
   "source": [
    "### Implement undo_last_move\n",
    "\n",
    "The undo_last_move function is used revert the quantum state to a state immediately before the last move that was executed. It has the following code signature.\n",
    "```\n",
    "undo_last_move( ) : { probabilities, empty_bitboard, full_bitboard }\n",
    "```\n",
    "It takes no arguments, and returns the same json object as the previous endpoints. The following code is an implementation of undo_last_move() that prints the resulting probability distribution."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "mKvAxkgiwRQY"
   },
   "outputs": [],
   "source": [
    "def undo_last_move(board):\n",
    "  board.clear_debug_log()\n",
    "\n",
    "  r = board.undo_last_move()\n",
    "  probs = board.get_probability_distribution()\n",
    "  print_game(board)\n",
    "\n",
    "  return {\n",
    "      'result' : r,\n",
    "      'probabilities' : probs,\n",
    "      'empty' : board.get_empty_squares_bitboard(),\n",
    "      'full' : board.get_full_squares_bitboard()\n",
    "  }\n",
    "\n",
    "r = init(global_board, 0xFFFF00000000FFFF)\n",
    "r = do_move(global_board, split_b1_a3_c3)\n",
    "r = undo_last_move(global_board)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "KhNWwpSrwxXI"
   },
   "source": [
    "## REST server implementation\n",
    "\n",
    "With the functionality in place, you can define server endpoints and run the server. Use the [flask_restful](https://flask-restful.readthedocs.io/en/latest/) framework to create a simple server that implements these enpoints. Flask-restful allows you to encapsulate the functionality you want in classes that inherit from Resource. You will need to install `flask-ngrok` to give the server an accessible URL:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "hHk0IZsfw9xv"
   },
   "outputs": [],
   "source": [
    "!pip install -q flask flask_restful flask-ngrok"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "6iBt9Yw4hBbe"
   },
   "source": [
    "Define the REST endpoints for the webserver:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "teRaid0y1DYC"
   },
   "outputs": [],
   "source": [
    "from flask import Flask, request, jsonify\n",
    "from flask_restful import Resource, Api\n",
    "from flask_ngrok import run_with_ngrok\n",
    "\n",
    "class Init(Resource):\n",
    "  def get(self):\n",
    "    return {'about': 'Init'}\n",
    "\n",
    "  def post(self):\n",
    "    print(request.get_json())\n",
    "    n = request.get_json()['init_basis_state']\n",
    "    global_board.clear_debug_log()\n",
    "    return init(global_board,int(n))\n",
    "\n",
    "    \n",
    "class DoMove(Resource):\n",
    "  def post(self):\n",
    "    move_json = request.get_json()\n",
    "    t = MoveType(move_json['type'])\n",
    "    # We need to convert square indices to square names.\n",
    "    move_json['square1'] = bit_to_square(move_json['square1'])\n",
    "    move_json['square2'] = bit_to_square(move_json['square2'])\n",
    "    move_json['square3'] = bit_to_square(move_json['square3'])\n",
    "\n",
    "    if t == MoveType.SPLIT_SLIDE or t == MoveType.SPLIT_JUMP:\n",
    "      return do_move(global_board, get_split_move(move_json))\n",
    "    elif t == MoveType.MERGE_JUMP or t == MoveType.MERGE_SLIDE:\n",
    "      return do_move(global_board, get_merge_move(move_json))\n",
    "    else:\n",
    "      return do_move(global_board, get_standard_move(move_json))\n",
    "\n",
    "class UndoLastMove(Resource):\n",
    "    def post(self):\n",
    "        return undo_last_move(global_board)\n",
    "\n",
    "app = Flask(__name__)\n",
    "run_with_ngrok(app)\n",
    "\n",
    "api = Api(app)\n",
    "\n",
    "api.add_resource(Init, '/quantumboard/init')\n",
    "api.add_resource(DoMove, '/quantumboard/do_move')\n",
    "api.add_resource(UndoLastMove, '/quantumboard/undo_last_move')\n",
    "\n",
    "@app.route(\"/\")\n",
    "def home():\n",
    "    return \"<h1>Running Flask on Google Colab!</h1>\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "r00rzroKgwZD"
   },
   "source": [
    "And start the local webserver:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "7qNJEYOEgX9I"
   },
   "outputs": [],
   "source": [
    "# docs_infra: no_execute\n",
    "\n",
    "app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "nRr6NmF72Yf8"
   },
   "source": [
    "The server should now be running, and can be tested with the [Quantum Chess Client](./quantum_chess_client.ipynb) notebook!"
   ]
  }
 ],
 "metadata": {
  "colab": {
   "collapsed_sections": [],
   "name": "quantum_chess_rest_api.ipynb",
   "toc_visible": true
  },
  "kernelspec": {
   "display_name": "Python 3",
   "name": "python3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
